use blip_buf::BlipBuf;

use super::{lengthcounter::LenghtCounter, sound::WAVE_INITIAL_DELAY};

pub(crate) struct WaveChannel {
    active: bool,
    dac_enabled: bool,
    length: LenghtCounter,
    frequency: u16,
    period: u32,
    last_amp: i32,
    delay: u32,
    volume_shift: u8,
    waveram: [u8; 16],
    current_wave: u8,
    dmg_mode: bool,
    sample_recently_accessed: bool,
    pub(crate) blip: BlipBuf,
}

impl WaveChannel {
    pub(crate) fn new(blip: BlipBuf, dmg_mode: bool) -> Self {
        Self {
            active: false,
            dac_enabled: false,
            length: LenghtCounter::new(256),
            frequency: 0,
            period: 2048,
            last_amp: 0,
            delay: 0,
            volume_shift: 0,
            waveram: [0; 16],
            current_wave: 0,
            dmg_mode: dmg_mode,
            sample_recently_accessed: false,
            blip: blip,
        }
    }

    pub(crate) fn rb(&self, addr: u16) -> u8 {
        match addr {
            0xFF1A => (if self.dac_enabled { 0x80 } else { 0 }) | 0x7F,
            0xFF1B => 0xFF,
            0xFF1C => 0x80 | ((self.volume_shift & 0b11) << 5) | 0x1F,
            0xFF1D => 0xFF,
            0xFF1E => 0x80 | if self.length.enabled { 0x40 } else { 0 } | 0x3F,
            // a range
            0xFF30..=0xFF3F => {
                if !self.active {
                    self.waveram[addr as usize - 0xFF30]
                } else {
                    if !self.dmg_mode || self.sample_recently_accessed {
                        self.waveram[self.current_wave as usize >> 1]
                    } else {
                        0xFF
                    }
                }
            }

            _ => unimplemented!("not imple yet"),
        }
    }

    pub(crate) fn wb(&mut self, addr: u16, value: u8, frame_step: u8) {
        match addr {
            0xFF1A => {
                self.dac_enabled = (value & 0x80) == 0x80;
                self.active = self.active && self.dac_enabled;
            }
            0xFF1B => self.length.set(value),
            0xFF1C => self.volume_shift = (value >> 5) & 0b11,
            0xFF1D => {
                self.frequency = (self.frequency & 0x0700) | (value as u16);
                self.calculate_period();
            }
            0xFF1E => {
                self.frequency = (self.frequency & 0x00FF) | (((value & 0b111) as u16) << 8);
                self.calculate_period();

                self.length.enable(value & 0x40 == 0x40, frame_step);
                self.active &= self.length.is_active();

                if value & 0x80 == 0x80 {
                    self.dmg_maybe_corrupt_waveram();

                    self.length.trigger(frame_step);

                    self.current_wave = 0;
                    self.delay = self.period + WAVE_INITIAL_DELAY;

                    if self.dac_enabled {
                        self.active = true;
                    }
                }
            }
            // a range
            0xFF30..=0xFF3F => {
                if !self.active {
                    self.waveram[addr as usize - 0xFF30] = value;
                } else {
                    if !self.dmg_mode || self.sample_recently_accessed {
                        self.waveram[self.current_wave as usize >> 1] = value;
                    }
                }
            }

            _ => unimplemented!("not impl yet"),
        }
    }

    fn dmg_maybe_corrupt_waveram(&mut self) {
        // corrupt when in dmg_mode, the channel is active
        // and when delay == 0
        // delay == 0 means that the next cycle will run the sample
        // and thus the corruption occurs
        // since the corruption occurs after this code
        // we need to increase current_wave by one as well
        if !self.dmg_mode || !self.active || self.delay != 0 {
            return;
        }

        let byteindex = ((self.current_wave + 1) % 32) as usize >> 1;

        if byteindex < 4 {
            self.waveram[0] = self.waveram[byteindex];
        } else {
            let blockstart = byteindex & 0b1100;
            self.waveram[0] = self.waveram[blockstart];
            self.waveram[1] = self.waveram[blockstart + 1];
            self.waveram[2] = self.waveram[blockstart + 2];
            self.waveram[3] = self.waveram[blockstart + 3];
        }
    }

    fn calculate_period(&mut self) {
        if self.frequency > 2048 {
            self.period = 0;
        } else {
            self.period = (2048 - self.frequency as u32) * 2;
        }
    }

    pub(crate) fn on(&self) -> bool {
        self.active
    }

    pub(crate) fn run(&mut self, start_time: u32, end_time: u32) {
        self.sample_recently_accessed = false;
        if !self.active || self.period == 0 {
            if self.last_amp != 0 {
                self.blip.add_delta(start_time, -self.last_amp);
                self.last_amp = 0;
                self.delay = 0;
            }
        } else {
            let mut time = start_time + self.delay;
            // a sample may be muted, 100%, 50% or 25%
            // to avoid loss of precision
            // the WaveChannel will output i32 samples at 4x the usual amplitude
            // this will be taken into account when mixing all the samples
            let volshift = match self.volume_shift {
                0 => 4 + 2, // to mute a 4 bit sample mutiplied by 2^2
                1 => 0,
                2 => 1,
                3 => 2,
                _ => unreachable!("can not be accessed"),
            };

            while time < end_time {
                let wavebyte = self.waveram[self.current_wave as usize >> 1];
                let sample = if self.current_wave % 2 == 0 {
                    wavebyte >> 4
                } else {
                    wavebyte & 0xF
                };

                // shifted by 2 so that 25% does not lose precision
                let amp = ((sample << 2) >> volshift) as i32;

                if amp != self.last_amp {
                    self.blip.add_delta(time, amp - self.last_amp);
                    self.last_amp = amp;
                }

                if time >= end_time - 2 {
                    // mark the wave sample as recently accessed
                    // the DMG only allows a wave sample to be read at this point
                    // in time
                    self.sample_recently_accessed = true;
                }

                time += self.period;
                self.current_wave = (self.current_wave + 1) % 32;
            }

            // next time, we have to wait an additional delay timesteps
            self.delay = time - end_time;
        }
    }

    pub(crate) fn step_length(&mut self) {
        self.length.step();
        self.active &= self.length.is_active();
    }
}
